package org.apache.derby.iapi.jdbc;

import org.apache.derby.iapi.services.monitor.ModuleFactory;
import org.apache.derby.iapi.services.monitor.Monitor;
import org.apache.derby.shared.common.sanity.SanityManager;

import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;

/**
 * @author ex_chenzy59
 * @since 2025/3/13
 *
 */
public final class DRDAServerStarter implements ModuleControl, Runnable {
    private Object server;
    private Method runServerMethod;
    private Method serverShutdownMethod;
    private PrintWriter consoleWriter = null;
    private String userArg = null;
    private String passwordArg = null;
    private InetAddress listenAddress = null;
    private int portNumber = -1;
    private Class<?> serverClass;
    private Thread serverThread;
    private static final String serverClassName = "org.apache.derby.impl.drda.NetworkServerControlImpl";

    @Override
    public void run() {
        try {
            runServerMethod.invoke(server, consoleWriter);
        } catch (InvocationTargetException ite) {
            Monitor.logTextMessage(MessageId.CONN_NETWORK_SERVER_START_EXCEPTION, ite.getTargetException().getMessage());
            ite.printStackTrace(Monitor.getStream().getPrintWriter());
            server = null;
        } catch (Exception e) {
            Monitor.logTextMessage(MessageId.CONN_NETWORK_SERVER_START_EXCEPTION, e.getMessage());
            e.printStackTrace(Monitor.getStream().getPrintWriter());
            server = null;
        }
    }

    @Override
    public void boot(boolean create, java.util.Properties properties) {
        if (server != null) {
            if (SanityManager.DEBUG){
                SanityManager.THROWASSERT("Network server starter module booted twice.");
            }
            return;
        }
        // Load the server class indirectly so that Derby does not require the network code
        try {
            serverClass = Class.forName(serverClassName);
        } catch (ClassNotFoundException cnfe) {
            Monitor.logTextMessage(MessageId.CONN_NETWORK_SERVER_CLASS_FIND, serverClassName);
            return;
        } catch (java.lang.Error e) {
            Monitor.logTextMessage(MessageId.CONN_NETWORK_SERVER_CLASS_LOAD, serverClassName,e.getMessage());
            return;
        }
        try {
            Constructor serverConstructor;
            try {
                serverConstructor = listenAddress == null?serverClass.getConstructor(String.class, String.class)
                        :serverClass.getConstructor(InetAddress.class,Integer.TYPE, String.class, String.class);
            } catch (Exception e1) {
                Monitor.logTextMessage(MessageId.CONN_NETWORK_SERVER_START_EXCEPTION, e1.getMessage());
                e1.printStackTrace(Monitor.getStream().getPrintWriter());
                return;
            }
            findStartStopMethods(serverClass);

            server = listenAddress == null?serverConstructor.newInstance(userArg, passwordArg)
                    :serverConstructor.newInstance(listenAddress, portNumber,userArg, passwordArg);
            serverThread = getMonitor().getDaemonThread(this, "NetworkServerStarter", false);
            serverThread.start();
        } catch (Exception e) {
            Monitor.logTextMessage(MessageId.CONN_NETWORK_SERVER_START_EXCEPTION, e.getMessage());
            server = null;
            e.printStackTrace(Monitor.getStream().getPrintWriter());
        }
    }
    private  static ModuleFactory getMonitor(){
        return Monitor.getMonitor();
    }
    private void findStartStopMethods(final Class<?> serverClass)throws NoSuchMethodException{
        // Methods are public so no need for privilege blocks.
        runServerMethod = serverClass.getMethod("blockingStart", PrintWriter.class);

        serverShutdownMethod = serverClass.getMethod("directShutdown", null);
    }
    public void setStartInfo(InetAddress listenAddress, int portNumber, String userName, String password, PrintWriter consoleWriter) {
        this.userArg = userName;
        this.passwordArg = password;
        setStartInfo(listenAddress, portNumber, consoleWriter);
    }

    public void setStartInfo(InetAddress listenAddress, int portNumber, PrintWriter consoleWriter) {
        this.listenAddress = listenAddress;
        this.portNumber = portNumber;

        // wrap the user-set consoleWriter with autoflush to true.
        // this will ensure that messages to console will be
        // written out to the consoleWriter on a println.
        // DERBY-1466
        this.consoleWriter = consoleWriter == null ? null : new PrintWriter(consoleWriter, true);
    }
}
